﻿// =====================================================================
//
//  This file is part of the Microsoft Dynamics CRM SDK code samples.
//
//  Copyright (C) Microsoft Corporation.  All rights reserved.
//
//  This source code is intended only as a supplement to Microsoft
//  Development Tools and/or on-line documentation.  See these other
//  materials for detailed information regarding Microsoft code samples.
//
//  THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//  PARTICULAR PURPOSE.
//
// =====================================================================

using System;
using System.IO;
using System.ServiceModel.Channels;
using System.Text;
using System.Xml;
using System.Xml.Linq;

#if SILVERLIGHT
using System.Security.Cryptography;
#else
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Security.Cryptography;
using Windows.Security.Cryptography.Core;
using Windows.Storage.Streams;
#endif

namespace SL.IdentityModel.Protocols.WSTrust
{
	/// <summary>
	/// Class inherits from MessageHeader, is used to add an issued token into the security header of SOAP message
	/// </summary>
	public class IssuedTokenHeader : MessageHeader
	{
		private static class TimestampConstants
		{
			public static XNamespace NamespaceUri = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd";
			public const string Timestamp = "Timestamp";
			public const string IdAttribute = "Id";
			public const string Created = "Created";
			public const string Expires = "Expires";
			public const string DateTimeStringFormat = "yyyy-MM-ddTHH:mm:ss.fffZ";
		}

		private static class SignatureConstants
		{
			public static XNamespace NamespaceUri = "http://www.w3.org/2000/09/xmldsig#";
			public const string Algorithm = "Algorithm";
			public const string CanonicalAlgorithmValue = "http://www.w3.org/2001/10/xml-exc-c14n#";
			public const string CanonicalizationMethod = "CanonicalizationMethod";
			public const string DigestMethod = "DigestMethod";
			public const string DigestAlgorithmValue = "http://www.w3.org/2000/09/xmldsig#sha1";
			public const string DigestValue = "DigestValue";
			public const string KeyInfo = "KeyInfo";
			public const string Reference = "Reference";
			public const string SignAlgorithmValue = "http://www.w3.org/2000/09/xmldsig#hmac-sha1";
			public const string Signature = "Signature";
			public const string SignedInfo = "SignedInfo";
			public const string SignatureMethod = "SignatureMethod";
			public const string SignatureValue = "SignatureValue";
			public const string Transform = "Transform";
			public const string Transforms = "Transforms";
			public const string URI = "URI";
		}

		private RequestSecurityTokenResponse _rstr = null;

		public override string Name
		{
			get { return "Security"; }
		}

		public override string Namespace
		{
			get { return "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"; }
		}

		/// <summary>
		/// Creates an instance of the class
		/// </summary>
		/// <param name="issuedToken">sets issued token to be used in the SOAP header</param>
		public IssuedTokenHeader(RequestSecurityTokenResponse rstr)
		{
			if (null == rstr)
			{
				throw new ArgumentNullException("rstr");
			}
			_rstr = rstr;
		}

		protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
		{
			writer.WriteNode(XmlReader.Create(new StringReader(_rstr.RequestedSecurityToken.RawToken)), false);

			if (String.Equals(_rstr.KeyType, WSTrust13Constants.KeyTypes.Symmetric))
			{
				XElement timestamp = CreateTimestampNode("_0");
				timestamp.WriteTo(writer);
				byte[] canonizedBytes = Encoding.UTF8.GetBytes(timestamp.ToString(SaveOptions.DisableFormatting));
				XElement signature = CreateSignatureNode("_0", canonizedBytes, _rstr.SymmetricProofTokenBytes, _rstr.ProofTokenReference);
				signature.WriteTo(writer);
			}
		}

		private static XElement CreateTimestampNode(string id)
		{
			XNamespace ns = TimestampConstants.NamespaceUri;

			DateTime now = DateTime.UtcNow;
			DateTime expiration = now.AddMinutes(5);

			XElement elem = new XElement(ns + TimestampConstants.Timestamp,
										 new XAttribute(XNamespace.Xmlns + "u", ns),
										 new XAttribute(ns + TimestampConstants.IdAttribute, id),
										 new XElement(ns + TimestampConstants.Created, now.ToString(TimestampConstants.DateTimeStringFormat)),
										 new XElement(ns + TimestampConstants.Expires, expiration.ToString(TimestampConstants.DateTimeStringFormat)));


			return elem;
		}

		private static XElement CreateSignatureNode(string referenceId, byte[] referenceBytes, byte[] keyBytes, XNode KeyInfoReference)
		{
			if (string.IsNullOrEmpty(referenceId))
			{
				throw new ArgumentNullException("referenceId");
			}

			if (null == referenceBytes)
			{
				throw new ArgumentNullException("referenceBytes");
			}

			if (null == keyBytes)
			{
				throw new ArgumentNullException("keyBytes");
			}

			XNamespace ns = SignatureConstants.NamespaceUri;

			string digestValue;
#if SILVERLIGHT
			using (SHA1 sha1 = new SHA1Managed())
			{
				digestValue = Convert.ToBase64String(sha1.ComputeHash(referenceBytes));
			}
#else
            HashAlgorithmProvider sha1 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha1);
            IBuffer sha1HashBuffer = sha1.HashData(referenceBytes.AsBuffer());
            digestValue = CryptographicBuffer.EncodeToBase64String(sha1HashBuffer);
#endif

			XElement elem = new XElement(ns + SignatureConstants.Signature,
				new XElement(ns + SignatureConstants.SignedInfo,
					new XElement(ns + SignatureConstants.CanonicalizationMethod,
						new XAttribute(SignatureConstants.Algorithm, SignatureConstants.CanonicalAlgorithmValue),
						""),
					new XElement(ns + SignatureConstants.SignatureMethod,
						new XAttribute(SignatureConstants.Algorithm, SignatureConstants.SignAlgorithmValue),
						""),
					new XElement(ns + SignatureConstants.Reference,
						new XAttribute(SignatureConstants.URI, "#" + referenceId),
						new XElement(ns + SignatureConstants.Transforms,
							new XElement(ns + SignatureConstants.Transform,
								new XAttribute(SignatureConstants.Algorithm, SignatureConstants.CanonicalAlgorithmValue),
								"")),
						new XElement(ns + SignatureConstants.DigestMethod,
							new XAttribute(SignatureConstants.Algorithm, SignatureConstants.DigestAlgorithmValue),
							""),
						new XElement(ns + SignatureConstants.DigestValue, digestValue))));

			string canonizedString = elem.FirstNode.ToString(SaveOptions.DisableFormatting);
			byte[] canonizedBytes = Encoding.UTF8.GetBytes(canonizedString);

            string signatureValue;
#if SILVERLIGHT
			using (HMACSHA1 hmacsha1 = new HMACSHA1(keyBytes))
			{
				signatureValue = Convert.ToBase64String(hmacsha1.ComputeHash(canonizedBytes));
			}
#else
            MacAlgorithmProvider hash = MacAlgorithmProvider.OpenAlgorithm(MacAlgorithmNames.HmacSha1);
            CryptographicKey key = hash.CreateKey(keyBytes.AsBuffer());
            IBuffer hmacSha1HashBuffer = CryptographicEngine.Sign(key, canonizedBytes.AsBuffer());
            signatureValue = CryptographicBuffer.EncodeToBase64String(hmacSha1HashBuffer);
#endif

			elem.Add(new XElement(ns + SignatureConstants.SignatureValue, signatureValue));
			if (null != KeyInfoReference)
			{
				elem.Add(new XElement(ns + SignatureConstants.KeyInfo, KeyInfoReference));
			}

			return elem;
		}

	}
}
